package com.chart.web.controller.chart;

import cn.hutool.core.util.XmlUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.chart.chart.domian.ChartOrders;
import com.chart.chart.domian.ChartPaidPackage;
import com.chart.chart.service.IChartCPaidPackageService;
import com.chart.chart.service.IChartOrdersService;
import com.chart.common.annotation.RateLimiter;
import com.chart.common.annotation.RepeatSubmit;
import com.chart.common.constant.CacheConstants;
import com.chart.common.core.controller.BaseController;
import com.chart.common.core.domain.AjaxResult;
import com.chart.common.core.domain.entity.SysUser;
import com.chart.common.core.domain.model.LoginUser;
import com.chart.common.core.redis.RedisCache;
import com.chart.common.utils.NonceUtils;
import com.chart.common.utils.QRCodeUtils;
import com.chart.common.utils.SecurityUtils;
import com.chart.common.utils.StringUtils;
import com.chart.common.utils.ip.IpUtils;
import com.chart.framework.web.service.TokenService;
import com.chart.system.domain.SysUserOrder;
import com.chart.system.service.ISysUserOrderService;
import com.chart.system.service.ISysUserService;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.codec.binary.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 图表套餐-微信支付
 *
 * @author chart
 */
@RestController
@RequestMapping("/paid/chart")
public class ChartPaidPackageController extends BaseController
{

    @Autowired
    private ISysUserService userService;

    @Autowired
    private IChartCPaidPackageService paidPackageService;

    @Autowired
    private ISysUserOrderService userOrderService;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private IChartOrdersService chartOrdersService;


    @Autowired
    private TokenService tokenService;

    // 令牌有效期（默认30分钟）
    @Value("${token.expireTime}")
    private int expireTime;

    /** 微信公众号APPID */
    @Value("${wxpay.appid}")
    private String appid;

    /** 微信公众号的商户号 */
    @Value("${wxpay.wxpaymentaccount}")
    private String wxpaymentaccount;

    /** 微信公众号的商户支付密钥 */
    @Value("${wxpay.apikey}")
    private String apikey;

    /** 下单接口 */
    @Value("${wxpay.basePath}")
    private String basePath;

    /** 回调接口 */
    @Value("${wxpay.notifyUrl}")
    private String notifyUrl;


    @ApiOperation("付费套餐：列表")
    @GetMapping("/package/list")
    public AjaxResult list(ChartPaidPackage chartPaidPackage)
    {
        if (ObjectUtils.isEmpty(chartPaidPackage.getPackageId())) {
            List<ChartPaidPackage> list = redisCache.getCacheObject(CacheConstants.CHART_PACKAGES);
            return AjaxResult.success(list);
        }
        ChartPaidPackage paidPackage = redisCache.getCacheObject(CacheConstants.CHART_PACKAGES + chartPaidPackage.getPackageId());
        return AjaxResult.success(paidPackage);
    }


    //存到缓存中  待支付
    @RepeatSubmit(interval = 3000)
    @ApiOperation("付费套餐：预下单")
    @PostMapping(value = "/order")
    @Transactional
    public AjaxResult getorder(@RequestBody ChartPaidPackage chartPaidPackage) {

        Map<String, Object> map = new HashMap<>();
        //获取当前用户
        Long userId = SecurityUtils.getUserId();
        if (SecurityUtils.isAdmin(userId)) {
            //封装返回值
            map.put("orderNum", "管理员无需付费");
            return AjaxResult.success(map);
        }

        if (chartPaidPackage.getPackageId()==1) {
            ChartOrders orders = chartOrdersService.getOne(new LambdaQueryWrapper<ChartOrders>().eq(ChartOrders::getUserId, userId).eq(ChartOrders::getPackageId, 1));
            if (!ObjectUtils.isEmpty(orders)) {
                map.put("orderNum", "用户只能购买一次试用会员");
                return AjaxResult.success(map);
            }
        }

        ChartOrders chartOrders = new ChartOrders();
        chartOrders.setUserId(userId);//用户
        chartOrders.setOrderNum(getOutTradeNo());//订单号
        chartOrders.setPackageId(chartPaidPackage.getPackageId());//商品
//        chartOrders.setQuantity(1);//数量
        BigDecimal decimal = chartPaidPackage.getTotalPrice().multiply(new BigDecimal(100));
        chartOrders.setTotalPrice(decimal.intValue());//总价
        chartOrders.setOrderDetails(chartPaidPackage.getPackageName());//描述
        //设置订单详情格式
        try {
            //将订单信息放到缓存里边
            redisCache.setCacheObject(CacheConstants.CHART_ORDER+chartOrders.getOrderNum(),chartOrders,120,TimeUnit.MINUTES);

                map.put("orderNum", chartOrders.getOrderNum());//订单号
//                map.put("orderId", chartOrders.getOrderId());//订单id
                return AjaxResult.success(map);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //封装返回值
        map.put("orderNum", "");
        return AjaxResult.success(map);
    }




    @ApiOperation("付费套餐：查询订单信息状态")
    @GetMapping(value = "/order/getStatus")
    @Transactional
    public AjaxResult SelectOrderStatus(String orderNum) {

        // 0 未支付   1 已支付
        //判断订单是否已经处理
        String s = redisCache.getCacheObject(CacheConstants.VIP_OFFER + orderNum);
        if (!StringUtils.isEmpty(s)) {
            return AjaxResult.success(1);
        }
        //获取订单信息
        ChartOrders orders = chartOrdersService.getOne(new LambdaQueryWrapper<ChartOrders>().eq(ChartOrders::getOrderNum,orderNum));
        if (ObjectUtils.isEmpty(orders)) {
            return AjaxResult.success(0);
        }
        if (orders.getStatus()==0) {
            return AjaxResult.success(0);
        }
        if (orders.getStatus()==1 && orders.getIsConsume()==1) {
            return AjaxResult.success(1);
        }
            if (orders.getStatus()==1 && orders.getIsConsume()==0) {
                ChartPaidPackage paidPackage = paidPackageService.getById(orders.getPackageId());
                paidPackage.setOrderId(orders.getOrderId());
                //获取订单vip的时间
                LocalDateTime experation = setExperation(paidPackage);
                if (!ObjectUtils.isEmpty(experation)) {
                    orders.setIsConsume(1);
                    boolean b = chartOrdersService.saveOrUpdate(orders);
                    if (b) {
                        //修改完用户信息  刷新token
                        refreshToken(experation,paidPackage.getPackageId());
                        //将处理过得订单信息进行缓存
                        redisCache.setCacheObject(CacheConstants.VIP_OFFER+orderNum,orderNum, 5,TimeUnit.MINUTES);
                        return AjaxResult.success(1);
                    }
                }
            }
        return AjaxResult.success(0);
    }


    /**
     * 支付主接口
     * */
    @RepeatSubmit(interval = 3000)
    @ApiOperation("付费套餐：支付主接口")
    @GetMapping(value = "/order/pay")
    public AjaxResult createQRCode(HttpServletResponse response,String orderNum) {
        ServletOutputStream sos = null;
        try {
            ChartOrders orders = redisCache.getCacheObject(CacheConstants.CHART_ORDER + orderNum);
            if (ObjectUtils.isEmpty(orders)) {
                return AjaxResult.error("订单已经过期请重新选择套餐！");
            }
            String orderInfo = createOrderInfo(orders);
            String code_url = httpOrder(orderInfo);//调用统一下单接口
            sos = response.getOutputStream();
            //生成二维码
            QRCodeUtils.encode(code_url, sos);
            return AjaxResult.success();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error();
    }


    /**
     * 微信回调函数
     * 支付成功后微信服务器会调用此方法，修改数据库订单状态
     */
    @PostMapping(value = "/wxPayCallBack.do")
    @Transactional
    public String wxPayCallBack(HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> resultMap = new HashMap<>();
        try {
            InputStream inStream = request.getInputStream();
            ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = inStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
            }
            outSteam.close();
            inStream.close();
            String result = new String(outSteam.toByteArray(), "utf-8");// 获取微信调用我们notify_url的返回信息
            Map<String, Object> map = XmlUtil.xmlToMap(result);
            if (map.get("result_code").equals("SUCCESS")) {
                //返回成功后修改订单状态
                String out_trade_no = map.get("out_trade_no").toString();
                ChartOrders order = chartOrdersService.getOne(new LambdaQueryWrapper<ChartOrders>().eq(ChartOrders::getOrderNum, out_trade_no));
                //判断订单是否已经保存
                if (ObjectUtils.isEmpty(order)) {
                    //判断是否有订单生成
                    ChartOrders orders = redisCache.getCacheObject(CacheConstants.CHART_ORDER + out_trade_no);
                    if (!ObjectUtils.isEmpty(orders)) {
                        orders.setStatus(1);
                        boolean b = chartOrdersService.saveOrUpdate(orders);
                        if (b) {
                            resultMap.put("result_code","SUCCESS");
                            resultMap.put("result_msg","OK");
                            return XmlUtil.mapToXmlStr(resultMap);
                        }else {
                            resultMap.put("result_code","FAIL");
                            resultMap.put("result_msg","数据处理失败");
                            return XmlUtil.mapToXmlStr(resultMap);
                        }
                    }
                }else {
                    resultMap.put("result_code","SUCCESS");
                    resultMap.put("result_msg","OK");
                    return XmlUtil.mapToXmlStr(resultMap);
                }
                }
        } catch (Exception e) {
            resultMap.put("result_code","FAIL");
            resultMap.put("result_msg","错误");
            return XmlUtil.mapToXmlStr(resultMap);
        }
        resultMap.put("result_code","FAIL");
        resultMap.put("result_msg","错误");
        return XmlUtil.mapToXmlStr(resultMap);
    }

    /**
     * 生成统一下单格式的订单，XML格式字符串
     *
     * @param orders
     * @return
     */
//    private String createOrderInfo(Long orderId) throws Exception {
//        return createOrderInfo(orderId);
//    }

    //生成订单信息
    private String createOrderInfo(ChartOrders orders) throws Exception {
//        ChartOrders orders = chartOrdersService.getOne(new LambdaQueryWrapper<ChartOrders>().eq(ChartOrders::getOrderNum,orderNum));

        //生成订单对象
        Map<String, Object> map = new HashMap<>();
        map.put("appid", appid);//公众账号ID
        map.put("mch_id", wxpaymentaccount);//商户号
        map.put("body", orders.getOrderDetails());//商品描述
        map.put("nonce_str", NonceUtils.createNonce(32));
        map.put("notify_url", notifyUrl);//通知地址
        map.put("out_trade_no", orders.getOrderNum());//订单号
        map.put("spbill_create_ip", IpUtils.getIpAddr());//终端ip
        map.put("trade_type", "NATIVE");//交易类型
        map.put("total_fee", orders.getTotalPrice());//总金额
        String sign = createSign(map, apikey,"MD5");
        map.put("sign", sign);//签名
        //将订单对象转为xml格式
        String s = null;
        try {
            return XmlUtil.mapToXmlStr(map);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new String(s.getBytes("UTF-8"));
    }


    /**
     * 调统一下单API
     *
     * @param orderInfo
     * @return
     */
    private String httpOrder(String orderInfo) {
        try {
            HttpURLConnection conn = (HttpURLConnection) new URL(basePath).openConnection();
            //加入数据
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);

            BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream());
            buffOutStr.write(orderInfo.getBytes("UTF-8"));
            buffOutStr.flush();
            buffOutStr.close();

            //获取输入流
            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));

            String line = null;
            StringBuffer sb = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }

            Map<String, Object> map = XmlUtil.xmlToMap(sb.toString());
            String return_msg = map.get("return_msg").toString();
//            System.out.println(return_msg);
            String return_code = map.get("return_code").toString();
            String code_url = map.get("code_url").toString();
            //根据微信文档return_code 和result_code都为SUCCESS的时候才会返回code_url
            if (null != map && "SUCCESS".equals(return_code)) {
                return code_url;
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    //修改用户vip信息
    private LocalDateTime setExperation(ChartPaidPackage chartPaidPackage)
    {
            Long userId = SecurityUtils.getUserId();
            LocalDateTime newTime = LocalDateTime.now();
            Long addTime = chartPaidPackage.getPackageValue();
            Long orderId = chartPaidPackage.getOrderId();
            LocalDateTime newExpirTime =null;
            boolean b=false;
            SysUserOrder userOrder =null;
            //判断用户是否是vip
        if (SecurityUtils.isVip()) {
                userOrder = userOrderService.getOne(new LambdaQueryWrapper<SysUserOrder>().eq(SysUserOrder::getUserId, userId));
                LocalDateTime expirTime = userOrder.getExpirationTime();
                //VIP还没到期
                if (expirTime.isAfter(newTime)) {
                    newExpirTime = expirTime.plusSeconds(addTime / 1000);
                }else {
                    newExpirTime = newTime.plusSeconds(addTime / 1000);
                }
                userOrder.setOrderId(orderId);
                userOrder.setExpirationTime(newExpirTime);

                if ( chartPaidPackage.getPackageId()==1) {
                    SysUser user = userService.getById(userId);
                    if (user.getIsApplyPackage()==0) {
                        user.setIsApplyPackage(1);
                    }
                    boolean b1 = userService.saveOrUpdate(user);
                }

                    b = userOrderService.saveOrUpdate(userOrder);

        }else {
                newExpirTime = newTime.plusSeconds(addTime / 1000);
                userOrder = new SysUserOrder();
                userOrder.setUserId(userId);
                userOrder.setOrderId(orderId);
                userOrder.setExpirationTime(newExpirTime);

                SysUser user = userService.getById(userId);
                user.setIsVip(1);
                if (chartPaidPackage.getPackageId()==1) {
                    user.setIsApplyPackage(1);
                }
                boolean b1 = userService.saveOrUpdate(user);
                if (b1) {
                    b = userOrderService.saveOrUpdate(userOrder);
                }
            }
        if (b) {
            return newExpirTime;
        }
        return null;
    }








    //将套餐信息进行缓存
    @PostConstruct
    private void extracted() {
        List<ChartPaidPackage> list = paidPackageService.list(new LambdaQueryWrapper<ChartPaidPackage>().ne(ChartPaidPackage::getStatus, 1));
        list.forEach(e->{
            redisCache.setCacheObject(CacheConstants.CHART_PACKAGES+e.getPackageId(),e);
        });
        redisCache.setCacheObject(CacheConstants.CHART_PACKAGES,list);
    }



    /**
     * 生成16位随机订单号
     *
     * @return key
     */
    private static String getOutTradeNo() {
        SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss", Locale.getDefault());
        Date date = new Date();
        String key = format.format(date);
        Random r = new Random();
        key = key + r.nextInt();
        key = key.replaceAll("-", "").substring(0, 15);
        return key;
    }


    /**
     * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
     *
     * @param data     待签名数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名
     */
    private static String createSign(final Map<String, Object> data, String key, String signType) throws Exception {
        //根据规则创建可排序的map集合
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals("") || ObjectUtils.isEmpty(k)) {
                continue;
            }
            String s = data.get(k).toString();
            if (!ObjectUtils.isEmpty(s) && s.trim().length() > 0) // 参数值为空，则不参与签名
            {
                sb.append(k).append("=").append(s.trim()).append("&");
            }
        }
        sb.append("key=").append(key);
        //转换UTF-8
        String str = new String(sb.toString().getBytes("UTF-8"));
        if ("MD5".equals(signType)) {
            return md5(sb.toString()).toUpperCase();
        }else if ("HMACSHA256".equals(signType)) {
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secret_key);
            byte[] array = sha256_HMAC.doFinal(str.getBytes("UTF-8"));
            StringBuilder sb2 = new StringBuilder();
            for (byte item : array) {
                sb2.append(Hex.encodeHexString(new byte[] { item }));
            }
            return sb2.toString();
        }
        else {
            throw new RuntimeException();
        }
    }


        /**
         * md5常用工具类
         * @param data
         * @return
         */
        private static String md5(String data) throws Exception {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(data.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b & 0xFF));
            }
            return sb.toString();
        }

     //每天凌晨自动处理过期的vip
    @Scheduled(cron = "0 0 0 1/1 * ? ")
    public void checkVip() {
        List<SysUser> newList = new ArrayList<>();
        //获取所有会员数据
        List<SysUser> VIPList = userService.list(new LambdaQueryWrapper<SysUser>().eq(SysUser::getIsVip,1));
        VIPList.forEach(e->{
            //根据id去查询vip订单数据
            SysUserOrder userOrder = userOrderService.getOne(new LambdaQueryWrapper<SysUserOrder>().eq(SysUserOrder::getUserId, e.getUserId()));
            //如果数据存在
            if (!ObjectUtils.isEmpty(userOrder)) {
                //获取vip过期时间比较
                LocalDateTime expirTime = userOrder.getExpirationTime();
                LocalDateTime now = LocalDateTime.now();
                if (expirTime.isBefore(now)) {
                    e.setIsVip(0);
                    newList.add(e);
                }
            }
        });
        userService.saveOrUpdateBatch(newList);

    }

    /**
     * 刷新令牌有效期
     *
     * @param experation 刷新会员时间
     */
    public void refreshToken(LocalDateTime experation,Long packageId)
    {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        SysUser user = loginUser.getUser();
        if (!SecurityUtils.isVip()) {
            user.setIsVip(1);
            if (packageId==1) {
                user.setIsApplyPackage(1);
            }
        }else {
            if (user.getIsApplyPackage()==0 && packageId==1) {
                user.setIsApplyPackage(1);
            }
        }
        user.setExpirationTime(experation);
        // 根据uuid将loginUser缓存
        String userKey = tokenService.getTokenKey1(loginUser.getToken());

        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }


}


